﻿using System;
using System.ComponentModel;
using System.Threading;

namespace ManagedScenarios
{
    /// <summary>
    /// Demonstrates leaking instances via event registration
    /// </summary>
    [Description("Demonstrates leaking instances via event registration")]
    public class ManagedMemoryLeakScenario : IScenario
    {
        public void Run(string[] args)
        {
            Console.WriteLine(@"
I am now creating instances in a loop and 'forgetting' to dispose 1 in 10
of these instances. Because the instances have registered themselves with
an event on a long-lived object (in this case the AppDomain) they will
not get GC'd (unless explicitly disposed first, which unwires the event)
To see what's going on, in Perfmon compare
    .Net Memory \ # bytes in all heaps
    Process \ Private bytes
");

            int total = 0;
            const int batchSize = 100000;
            var cancel = new ManualResetEvent(false);
            var workerThread = new Thread(() =>
                                             {
                                                 while (true)
                                                 {
                                                     for (int i = 0; i < batchSize; i++)
                                                     {
                                                         total += i;

                                                         // The normal scenario here is that your 'worker' is a UI form
                                                         // or viewmodel or something, and you hook a global event
                                                         // without realising that it keeps your viewmodel from being GC'd
                                                         // even after it is no longer 'on screen' 

                                                         var instance = new Worker(total);
                                                         if (total % 10 != 0)
                                                             instance.Dispose();    // 'forget' to dispose 1/10th of them

                                                         if (cancel.WaitOne(0))     // quick cancel check within the loop
                                                             return;
                                                     }

                                                     // sleep / check for cancellation
                                                     if (cancel.WaitOne(2000))
                                                         return;
                                                 }
                                             });
            workerThread.Start();
            Console.WriteLine("Press any RETURN to abort");
            Console.ReadLine();

            cancel.Set();
            workerThread.Join();
        }

        internal class Worker : IDisposable
        {
            private readonly int _total;
            private byte[] _data;

            public Worker(int total)
            {
                _total = total;
                _data = new byte[1024 * 16];

                // ...or any other long-lived event (power management is a good one)
                AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainOnAssemblyLoad;
            }

            private void CurrentDomainOnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
            {
            }

            public void Dispose()
            {
                AppDomain.CurrentDomain.AssemblyLoad -= CurrentDomainOnAssemblyLoad;
            }
        }
    }
}
